package pl.edu.agh.anomalyids.network.sniffer;

import java.util.Date;
import java.util.List;

import org.jnetpcap.packet.PcapPacket;
import org.jnetpcap.packet.PcapPacketHandler;
import org.jnetpcap.protocol.network.Icmp;
import org.jnetpcap.protocol.network.Ip4;
import org.jnetpcap.protocol.tcpip.Http;
import org.jnetpcap.protocol.tcpip.Http.Request;
import org.jnetpcap.protocol.tcpip.Tcp;
import org.jnetpcap.protocol.tcpip.Udp;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import pl.edu.agh.anomalyids.network.Packet;
import pl.edu.agh.anomalyids.network.Packet.Protocol;
import pl.edu.agh.anomalyids.network.header.impl.HTTPHeader;
import pl.edu.agh.anomalyids.network.header.impl.ICMPHeader;
import pl.edu.agh.anomalyids.network.header.impl.IPv4Header;
import pl.edu.agh.anomalyids.network.header.impl.TCPHeader;
import pl.edu.agh.anomalyids.network.header.impl.UDPHeader;
import pl.edu.agh.anomalyids.network.listener.PacketListener;

public class PacketHandler implements PcapPacketHandler<String> {  
	
	private static final Logger logger = LoggerFactory.getLogger(PacketHandler.class);
	
	private List<PacketListener> listeners;
	
	public PacketHandler(List<PacketListener> listeners) {
		this.listeners = listeners;
	}
	
    public void nextPacket(PcapPacket pcapPacket, String user) {  
    	Packet packet = createPacket(pcapPacket);
    	
    	logger.debug(packet.toString());
    	
    	synchronized(listeners) {
    		for (PacketListener listener : listeners) {
    			listener.onPacketArrived(packet);
    		}
    	}
    }
    
    private Packet createPacket(PcapPacket pcapPacket) {
    	Packet packet = new Packet();
    	
    	if (pcapPacket.hasHeader(Tcp.ID)) {
    		packet.addProtocol(Protocol.TCP, processTCPPacket(pcapPacket.getHeader(new Tcp())));
    	}
    	
    	if (pcapPacket.hasHeader(Http.ID)) {
    		packet.addProtocol(Protocol.HTTP, processHTTPPacket(pcapPacket.getHeader(new Http())));
    	}
    	
    	if (pcapPacket.hasHeader(Ip4.ID)) {
    		packet.addProtocol(Protocol.IPv4, processIPv4Packet(pcapPacket.getHeader(new Ip4())));
    	}
    	
    	if (pcapPacket.hasHeader(Icmp.ID)) {
    		packet.addProtocol(Protocol.ICMP, processICMPPacket(pcapPacket.getHeader(new Icmp())));
    	}
    	
    	if (pcapPacket.hasHeader(Udp.ID)) {
    		packet.addProtocol(Protocol.UDP, processUDPPacket(pcapPacket.getHeader(new Udp())));
    	}
    	
    	packet.setTime(new Date(pcapPacket.getCaptureHeader().timestampInMillis()));
    	
    	return packet;
    }
    
    private TCPHeader processTCPPacket(Tcp tcp) {
    	TCPHeader tcpHeader = new TCPHeader();
    	
    	tcpHeader.setSrcPort(tcp.source());
    	tcpHeader.setDestPort(tcp.destination());
    	tcpHeader.setFlags(tcp.flags());
    	
    	return tcpHeader;
    }
    
    private HTTPHeader processHTTPPacket(Http http) {
    	HTTPHeader httpHeader = new HTTPHeader();

    	String requestMethod = http.fieldValue(Request.RequestMethod);
    	if (requestMethod != null) {
    		httpHeader.setRequestMethod(requestMethod);
    	} else {
    		httpHeader.setRequestMethod("");
    	}
    	
    	return httpHeader;
    }
    
    private IPv4Header processIPv4Packet(Ip4 ip4) {
    	IPv4Header ipv4Header = new IPv4Header();
    	
    	ipv4Header.setSrcAddress(byteArrayToIp(ip4.source()));
    	ipv4Header.setDestAddress(byteArrayToIp(ip4.destination()));
    	ipv4Header.setTTL(ip4.ttl());
    	
    	return ipv4Header;
    }
    
    private ICMPHeader processICMPPacket(Icmp icmp) {
    	ICMPHeader icmpHeader = new ICMPHeader();
    	
    	icmpHeader.setType(icmp.type());
    	icmpHeader.setCode(icmp.code());
    	
    	return icmpHeader;
    }

    private UDPHeader processUDPPacket(Udp udp) {
    	UDPHeader udpHeader = new UDPHeader();
    	
    	udpHeader.setSrcPort(udp.source());
    	udpHeader.setDestPort(udp.destination());
    	
    	return udpHeader;
    }
    
    private String byteArrayToIp(byte[] bytes) {
    	StringBuilder builder = new StringBuilder();
    	
    	for (int i = 0; i < 3; ++i) {
    		builder.append(bytes[i] & 0xFF);
    		builder.append(".");
    	}
    	
    	builder.append(bytes[3] & 0xFF);
    	
    	return builder.toString();
    }
}
